///\todo upgrade

//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2012 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#ifndef _BUOLA_IO_CPACKET_H_
#define _BUOLA_IO_CPACKET_H_

#include <buola/buola.h>
#include <buola/io/curi.h>
#include <buola/io/serialization.h>
#include <buola/iterator/crange.h>
#include <map>

namespace buola { namespace io {

DECLARE_NAME(InStack);
DECLARE_NAME(Header);

class CPacketBase
{
public:
    static const size_t cMaxPacketSize=16777216;
    
protected:
    explicit CPacketBase(size_t pHeaderSize,size_t pSizeHint=256)
        :   mAllocSize(std::min(cMaxPacketSize,pSizeHint))
        ,   mData(alloc_array<char>(mAllocSize))
        ,   mOwnData(true)
        ,   mWritePtr(pHeaderSize)
        ,   mReadPtr(pHeaderSize)
        ,   mPassSocket(nullptr)
    {}

    ///this doesn't make a copy of the data, so we must keep the memory until we are done with it
    CPacketBase(size_t pHeaderSize,char *pData,std::size_t pSize)
        :   mAllocSize(pSize)
        ,   mData(pData)
        ,   mOwnData(false)
        ,   mWritePtr(pSize)
        ,   mReadPtr(pHeaderSize)
        ,   mPassSocket(nullptr)
    {}

    CPacketBase(const CPacketBase&)=delete;
    CPacketBase &operator=(const CPacketBase&)=delete;
    
    ~CPacketBase()
    {
        if(mOwnData)
            free(mData);
    }

    CPacketBase(NInStack,size_t pHeaderSize,char *pData,std::size_t pSize)
        :   mAllocSize(pSize)
        ,   mData(pData)
        ,   mOwnData(false)
        ,   mWritePtr(pHeaderSize)
        ,   mReadPtr(pHeaderSize)
        ,   mPassSocket(nullptr)
    {}

public:
    void PushData(const void *pData,std::size_t pSize)
    {
        AllocateFor(mWritePtr+pSize);
        memcpy(mData+mWritePtr,pData,pSize);
        mWritePtr+=pSize;
    }

    void PushSizeAndData(const void *pData,std::size_t pSize)
    {
        AllocateFor(mWritePtr+pSize+sizeof(uint64_t));
        uint64_t lSize=pSize;
        memcpy(mData+mWritePtr,&lSize,sizeof(uint64_t));
        memcpy(mData+mWritePtr+sizeof(uint64_t),pData,pSize);
        mWritePtr+=pSize+sizeof(uint64_t);
    }

    void Resize(size_t pSize)
    {
        AllocateFor(pSize);
        mWritePtr=pSize;
    }
    
    template<typename tPOD>
    OEnableIf<QIsPOD<tPOD>,CPacketBase&> operator<<(const tPOD &pV)
    {
        PushData(&pV,sizeof(pV));
        return *this;
    }

    CPacketBase &operator<<(const std::string &pV)
    {
        PushSizeAndData(pV.data(),pV.length());
        return *this;
    }
    
    CPacketBase &operator<<(const std::wstring &pV)
    {
        return operator<<(utf8(pV));
    }
    
    CPacketBase &operator<<(const io::CURI &pV)
    {
        return operator<<(pV.ToString());
    }

    CPacketBase &operator<<(const CRange<const uint8_t*> &pR)
    {
        PushData(pR.first,pR.second-pR.first);
        return *this;
    }
    
    const char *PopData(std::size_t pSize)
    {
        if(mReadPtr+pSize>mWritePtr)
            throw XNotEnough("trying to read more data from packet that is available");

        const char *lRet=mData+mReadPtr;
        mReadPtr+=pSize;
        return lRet;
    }

    void PopData(void *pData,std::size_t pSize)
    {
        memcpy(pData,PopData(pSize),pSize);
    }

    size_t PopSize()
    {
        uint64_t lSize;
        PopData(&lSize,sizeof(uint64_t));
        return lSize;
    }
    
    template<typename tPOD>
    OEnableIf<QIsPOD<tPOD>,CPacketBase&> operator>>(tPOD &pV)
    {
        PopData(&pV,sizeof(pV));
        return *this;
    }

    CPacketBase &operator>>(std::string &pV)
    {
        pV.resize(PopSize());
        PopData(&pV[0],pV.length());
        return *this;
    }
    
    CPacketBase &operator>>(std::wstring &pV)
    {
        std::string lS;
        operator>>(lS);
        pV=utf32(lS);
        return *this;
    }
    
    CPacketBase &operator>>(io::CURI &pV)
    {
        std::string lS;
        operator>>(lS);
        pV=io::uri(lS);
        return *this;
    }
    
    CPacketBase &operator>>(const CRange<uint8_t*> &pR)
    {
        PopData(pR.first,pR.second-pR.first);
        return *this;
    }
    
private:
    ///\todo check why the non-template enable_if version doesn't work for clang!
    template<int N,typename... tArgs,typename std::enable_if<N==sizeof...(tArgs),int>::type=0>
    void ExtractTupleImpl(std::tuple<tArgs...> &pArgs)
    {
    }

    template<int N,typename... tArgs,typename std::enable_if<(N<sizeof...(tArgs)),int>::type=0>
    void ExtractTupleImpl(std::tuple<tArgs...> &pArgs)
    {
        *this >> std::get<N>(pArgs);
        ExtractTupleImpl<N+1>(pArgs);
    }

    ///\todo check why the non-template enable_if version doesn't work for clang!
    template<int N,typename... tArgs,typename std::enable_if<N==sizeof...(tArgs),int>::type=0>
    void InsertTupleImpl(const std::tuple<tArgs...> &pArgs)
    {
    }

    template<int N,typename... tArgs,typename std::enable_if<(N<sizeof...(tArgs)),int>::type=0>
    void InsertTupleImpl(const std::tuple<tArgs...> &pArgs)
    {
        *this << std::get<N>(pArgs);
        InsertTupleImpl<N+1>(pArgs);
    }

    void PutMany() {}
    
    template<typename tFirst,typename... tRest>
    void PutMany(const tFirst &pFirst,const tRest&... pRest)
    {
        *this << pFirst;
        PutMany(pRest...);
    }
    
public:    
    template<typename... tArgs>
    CPacketBase &operator>>(std::tuple<tArgs...> &pTuple)
    {
        ExtractTupleImpl<0>(pTuple);
        return *this;
    }
    
    template<typename... tArgs>
    CPacketBase &operator<<(const std::tuple<tArgs...> &pTuple)
    {
        InsertTupleImpl<0>(pTuple);
        return *this;
    }
    
    template<typename... tArgs>
    void Put(const tArgs&... pArgs)
    {
        PutMany(pArgs...);
    }
    
    char *Data()                    {   return mData;       }
    const char *Data() const        {   return mData;       }
    std::size_t Size() const        {   return mWritePtr;   }

    void SetPassSocket(io::PSocket pSocket)    {   mPassSocket=std::move(pSocket);     }
    io::PSocket &PassSocket()                  {   return mPassSocket;                 }
    const io::PSocket &PassSocket() const      {   return mPassSocket;                 }

private:
    void AllocateFor(std::size_t pSize)
    {
        if(pSize<=mAllocSize) return;

        if(pSize>cMaxPacketSize)
            throw XRange("trying to allocate packet larger than maximum packet size");

        std::size_t lMaxPacketSize=cMaxPacketSize;
        mAllocSize+=std::min(lMaxPacketSize,std::max(2*(pSize-mAllocSize),mAllocSize/2));
        
        if(mOwnData)
        {
            realloc_array(mData,mAllocSize);
        }
        else
        {
            char *lOldData=mData;
            mData=alloc_array<char>(mAllocSize);
            memcpy(mData,lOldData,mWritePtr);
            mOwnData=true;
        }
    }

    std::size_t mAllocSize;  //includes header
    char *mData;        //points to start of data, i.e. header
    bool mOwnData;      //true if the data has been dynamically allocated by us (false if stack-allocated)
    std::size_t mWritePtr;   //includes header
    std::size_t mReadPtr;   //includes header

    io::PSocket mPassSocket; //optional socket to be passed or received with the packet (through AF_UNIX sockets)
};

template<typename tHeader>
class CPacket : public CPacketBase
{
public:
    static const size_t cHeaderSize=sizeof(tHeader);
    
    CPacket()
        :   CPacketBase(cHeaderSize)
    {}

    template<typename... tArgs>
    CPacket(NHeader,tArgs&&... pArgs)
        :   CPacketBase(cHeaderSize)
    {
        new(&Header()) tHeader(std::forward<tArgs>(pArgs)...);
    }
    
    explicit CPacket(std::size_t pSizeHint)
        :   CPacketBase(cHeaderSize,pSizeHint)
    {}

    ///this doesn't make a copy of the packet, so we must keep the memory until we are done with it
    CPacket(char *pData,std::size_t pSize)
        :   CPacketBase(cHeaderSize,pData,pSize)
    {}
    
protected:
    CPacket(NInStack,char *pData,size_t pSize)
        :   CPacketBase(nInStack,cHeaderSize,pData,pSize)
    {}

public:
    tHeader &Header()               {   return *reinterpret_cast<tHeader*>(Data()); }
    const tHeader &Header() const   {   return *reinterpret_cast<const tHeader*>(Data()); }

    char *Body()                    {   return Data()+sizeof(tHeader);  }
    const char *Body() const        {   return Data()+sizeof(tHeader);  }
    
    //! returns the size of the packet, including the header
    static std::size_t GetSizeFromHeader(char *pHeader)
    {
        tHeader *lHeader=reinterpret_cast<tHeader*>(pHeader);
        return lHeader->mSize;
    }
};

template<typename tHeader>
class CStackPacket : public CPacket<tHeader>
{
public:
    static const std::size_t cStackPacketSize=1024;
    
    CStackPacket()
        :   CPacket<tHeader>(nInStack,mStackData,cStackPacketSize)
    {}

    template<typename... tArgs>
    CStackPacket(NHeader,tArgs&&... pArgs)
        :   CPacket<tHeader>(nInStack,mStackData,cStackPacketSize)
    {
        new(&this->Header()) tHeader(std::forward<tArgs>(pArgs)...);
    }

private:
    char mStackData[cStackPacketSize];
};

template<typename tType>
auto operator>>(CPacketBase &pS,tType &pType) -> OFirst<CPacketBase&,decltype(deserialize(pS,pType))>
{
    deserialize(pS,pType);
    return pS;
}

template<typename tType>
auto operator<<(CPacketBase &pS,const tType &pType) -> OFirst<CPacketBase&,decltype(serialize(pS,pType))>
{
    serialize(pS,pType);
    return pS;
}

/*namespace io*/ } /*namespace buola*/ }

#include <buola/io/csocket.h>

#endif
